package middleware

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	jwt2 "github.com/golang-jwt/jwt/v5"
	"happy-book/internal/web/handler"
	"happy-book/internal/web/jwt"
	"net/http"
	"strings"
	"time"
)

const (
	// RefreshInterval 刷新Token的时间间隔
	RefreshInterval = time.Minute
)

var (
	ErrInvalidToken = errors.New("invalid jwt token")
)

// CheckLogin 登录校验
func CheckLogin() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//某些请求需要跳过登录校验
		if skipCheck(ctx) {
			ctx.Next() //继续执行后续middleware
			return
		}

		//解析JWT Token
		token, uc, err := parseTokenStr(ctx)
		if err != nil { //解析Token失败
			//TODO 打印异常日志
			fmt.Printf("parse jwt token error! %v\n", err)
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		//校验Token有效性
		now := time.Now()
		expireAt := uc.ExpiresAt
		if !token.Valid || expireAt.Before(now) { //Token无效或已经过期
			//TODO 打印异常日志
			fmt.Printf("invalid jwt token! %v\n", err)
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		//使用User-Agent作为辅助校验信息,避免JWT Token泄露造成的安全问题
		//对于APP端,更好地方式是使用device_info设备信息
		//对于PC/Web端,更好的方式是使用浏览器指纹,即浏览器的内核、版本和操作系统等核心信息生成的Hash值
		//浏览器指纹通常由前端生成,需要尽可能屏蔽浏览器版本升级所造成的影响
		ua := ctx.GetHeader(jwt.UserAgentHeaderKey)
		if uc.UserAgent != ua {
			//TODO 打印异常日志
			fmt.Printf("invalid User-Agent %v\n", ua)
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		//登录校验通过,将uid保存到Context,方便后续业务从Context中直接获取
		ctx.Set(handler.UidKey, uc.Uid)

		//刷新Token有效期
		refreshTokenIfNeeded(ctx, uc)
	}
}

// 是否跳过登录校验
func skipCheck(ctx *gin.Context) bool {
	path := ctx.Request.URL.Path
	return path == "/user/sign_up" || path == "/user/login"
}

// 解析Token
func parseTokenStr(ctx *gin.Context) (*jwt2.Token, *jwt.UserClaims, error) {
	//从Request Header中读取Token,格式是 Bearer xxxx
	auth := ctx.GetHeader("Authorization")
	if auth == "" { //无法获取Authorization Header
		return nil, nil, ErrInvalidToken
	}

	split := strings.Split(auth, " ")
	if len(split) != 2 { //Authorization Header不合法
		return nil, nil, ErrInvalidToken
	}

	//解析JWT Token
	tokenStr := split[1]
	var uc jwt.UserClaims
	token, err := jwt2.ParseWithClaims(tokenStr, &uc, func(token *jwt2.Token) (interface{}, error) {
		return []byte(jwt.SignatureKey), nil
	})
	if err != nil { //解析Token失败
		return nil, nil, err
	}

	return token, &uc, nil
}

// 跟需要刷新Token
func refreshTokenIfNeeded(ctx *gin.Context, uc *jwt.UserClaims) {
	//未到刷新时间,不刷新
	leftTime := uc.ExpiresAt.Sub(time.Now())
	if leftTime > jwt.ExpireTime-RefreshInterval {
		return
	}

	newToken, err := jwt.GenerateLoginToken(ctx, uc.Uid)
	if err != nil { //刷新Token失败,打印日常日志,不中断业务执行
		//TODO 打印异常日志
		fmt.Printf("refresh jwt token error! %v\n", err)
	} else { //刷新Token,更新Response Header
		ctx.Header(jwt.TokenHeaderKey, newToken)
	}
}
